﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Flagwind.Runtime.Serialization;

namespace Flagwind.Externals.Json
{
    public class JsonSerializer : Flagwind.Runtime.Serialization.ISerializer, Flagwind.Runtime.Serialization.ITextSerializer
    {
        #region 单例字段

        public static readonly JsonSerializer Default = new JsonSerializer();

        #endregion

        #region 成员字段

        private Flagwind.Runtime.Serialization.TextSerializationSettings _settings;

        #endregion

        #region 公共属性

        public Flagwind.Runtime.Serialization.TextSerializationSettings Settings
        {
            get
            {
                return _settings;
            }
        }

        #endregion

        #region 构造方法

        public JsonSerializer() : this(null)
        {
        }

        public JsonSerializer(Flagwind.Runtime.Serialization.TextSerializationSettings settings)
        {
            _settings = settings ?? new Flagwind.Runtime.Serialization.TextSerializationSettings();
        }

        #endregion

        #region 公共方法

        public string Serialize(object graph, TextSerializationSettings settings = null)
        {
            if(graph == null)
                return null;

            return Newtonsoft.Json.JsonConvert.SerializeObject(graph, this.GetSerializerSettings(settings ?? _settings));
        }

        public void Serialize(Stream serializationStream, object graph, SerializationSettings settings = null)
        {
            if(serializationStream == null)
                throw new ArgumentNullException("serializationStream");

            if(graph == null)
                return;

            using(var writer = new StreamWriter(serializationStream, System.Text.Encoding.UTF8, 1024, true))
            {
                var serializer = this.GetSerializer(settings);
                serializer.Serialize(writer, graph);
            }
        }

        public void Serialize(TextWriter writer, object graph, TextSerializationSettings settings = null)
        {
            if(writer == null)
                throw new ArgumentNullException("writer");

            if(graph == null)
                return;

            var serializer = this.GetSerializer(settings);
            serializer.Serialize(writer, graph);
        }

        public T Deserialize<T>(Stream serializationStream)
        {
            return (T)this.Deserialize(serializationStream, typeof(T));
        }

        public object Deserialize(Stream serializationStream, Type type)
        {
            if(serializationStream == null)
                throw new ArgumentNullException("serializationStream");

            using(var reader = new StreamReader(serializationStream, System.Text.Encoding.UTF8))
            {
                var serializer = this.GetSerializer(_settings);
                serializer.TypeNameHandling = TypeNameHandling.Objects;
                return serializer.Deserialize(reader, type);
            }
        }

        public T Deserialize<T>(TextReader reader)
        {
            return (T)this.Deserialize(reader, typeof(T));
        }

        public object Deserialize(TextReader reader, Type type)
        {
            if(reader == null)
                throw new ArgumentNullException("reader");

            var serializer = this.GetSerializer(_settings);
            serializer.TypeNameHandling = TypeNameHandling.Objects;
            return serializer.Deserialize(reader, type);
        }

        public T Deserialize<T>(string text)
        {
            return (T)this.Deserialize(text, typeof(T));
        }

        public object Deserialize(string text, Type type)
        {
            if(string.IsNullOrWhiteSpace(text))
                return null;

            using(var reader = new StringReader(text))
            {
                var serializer = this.GetSerializer(_settings);
                serializer.TypeNameHandling = TypeNameHandling.Objects;
                return serializer.Deserialize(reader, type);
            }
        }

        #endregion

        #region 私有方法

        private Newtonsoft.Json.JsonSerializerSettings GetSerializerSettings(Flagwind.Runtime.Serialization.TextSerializationSettings settings)
        {
            var result = new Newtonsoft.Json.JsonSerializerSettings()
            {
                Formatting = Formatting.None,
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                TypeNameHandling = (settings != null && settings.Typed) ? Newtonsoft.Json.TypeNameHandling.Objects : TypeNameHandling.None,
                ContractResolver = new MyJsonContractResolver((settings == null ? SerializationNamingConvention.None : settings.NamingConvention)),
            };

            if(settings != null)
            {
                result.Formatting = settings.Indented ? Newtonsoft.Json.Formatting.Indented : Newtonsoft.Json.Formatting.None;
                result.DateFormatHandling = settings.DateFormatHandling == Flagwind.Runtime.Serialization.DateFormatHandling.IsoDateFormat ? Newtonsoft.Json.DateFormatHandling.IsoDateFormat : Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;
                result.DateFormatString = settings.DateFormatString;

                if((settings.SerializationBehavior & SerializationBehavior.IgnoreDefaultValue) == SerializationBehavior.IgnoreDefaultValue)
                    result.DefaultValueHandling = DefaultValueHandling.Ignore;
            }

            result.Converters.Add(new Converters.Int64Converter());     // 加入 long 类型的转换器
            result.Converters.Add(new Converters.ObjectConverter());    // 加入未确定类型的转换器(一律转换成字典)

            return result;
        }

        private Newtonsoft.Json.JsonSerializer GetSerializer(Flagwind.Runtime.Serialization.SerializationSettings settings)
        {
            var jsonSettings = this.GetSerializerSettings(settings as TextSerializationSettings ?? _settings);
            return Newtonsoft.Json.JsonSerializer.Create(jsonSettings);
        }

        #endregion

        #region 显式实现

        Flagwind.Runtime.Serialization.SerializationSettings Flagwind.Runtime.Serialization.ISerializer.Settings
        {
            get
            {
                return this.Settings;
            }
        }

        object Flagwind.Runtime.Serialization.ISerializer.Deserialize(Stream serializationStream)
        {
            if(serializationStream == null)
                return null;

            using(var reader = new StreamReader(serializationStream))
            {
                return JsonConvert.DeserializeObject(reader.ReadToEnd());
            }
        }

        object Flagwind.Runtime.Serialization.ITextSerializer.Deserialize(TextReader reader)
        {
            if(reader == null)
                return null;

            return JsonConvert.DeserializeObject(reader.ReadToEnd());
        }

        object Flagwind.Runtime.Serialization.ITextSerializer.Deserialize(string text)
        {
            return JsonConvert.DeserializeObject(text);
        }

        #endregion

        #region 嵌套子类

        private class MyJsonContractResolver : Newtonsoft.Json.Serialization.DefaultContractResolver
        {
            private SerializationNamingConvention _namingConvention;

            public MyJsonContractResolver(SerializationNamingConvention namingConvention)
            {
                _namingConvention = namingConvention;
            }

            protected override JsonObjectContract CreateObjectContract(Type objectType)
            {
                var contract = base.CreateObjectContract(objectType);

                this.SetObjectCreator(contract);

                return contract;
            }

            protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
            {
                var properties = base.CreateProperties(type, memberSerialization);

                foreach(var property in properties)
                {
                    var attributes = property.AttributeProvider.GetAttributes(typeof(Flagwind.Runtime.Serialization.SerializationMemberAttribute), true);

                    if(attributes != null && attributes.Count > 0)
                    {
                        var attribute = (Flagwind.Runtime.Serialization.SerializationMemberAttribute)attributes[0];

                        if(!string.IsNullOrWhiteSpace(attribute.Name))
                        {
                            switch(_namingConvention)
                            {
                                case SerializationNamingConvention.Camel:
                                    property.PropertyName = ToNamingCase(attribute.Name, true);
                                    break;
                                case SerializationNamingConvention.Pascal:
                                    property.PropertyName = ToNamingCase(attribute.Name, false);
                                    break;
                                default:
                                    property.PropertyName = attribute.Name;
                                    break;
                            }
                        }

                        property.Ignored = (attribute.Behavior == SerializationMemberBehavior.Ignored);
                        property.Required = (attribute.Behavior == SerializationMemberBehavior.Required) ? Required.AllowNull : Required.Default;
                    }
                }

                return properties;
            }

            public override JsonContract ResolveContract(Type type)
            {
                var contract = base.ResolveContract(type);
                return contract;
            }

            protected override JsonConverter ResolveContractConverter(Type objectType)
            {
                var converter = base.ResolveContractConverter(objectType);
                return converter;
            }

            protected override string ResolvePropertyName(string propertyName)
            {
                switch(_namingConvention)
                {
                    case SerializationNamingConvention.Camel: return ToNamingCase(propertyName, true);
                    case SerializationNamingConvention.Pascal: return ToNamingCase(propertyName, false);
                    default: return base.ResolvePropertyName(propertyName);
                }
            }

            private void SetObjectCreator(JsonObjectContract contract)
            {
                if(contract.CreatorParameters.Count > 0)
                    return;

                //获取以构造函数参数的数量多少为排序依据的构造函数信息集合
                var constructors = System.Linq.Enumerable.OrderByDescending(contract.CreatedType.GetConstructors(), info => info.GetParameters().Length);

                foreach(var constructor in constructors)
                {
                    var parameters = constructor.GetParameters();

                    foreach(var parameter in parameters)
                    {
                        var property = contract.Properties.GetProperty(parameter.Name, StringComparison.OrdinalIgnoreCase);

                        if(property == null)
                            break;

                        contract.CreatorParameters.AddProperty(property);
                    }

                    if(parameters.Length != contract.CreatorParameters.Count)
                    {
                        contract.CreatorParameters.Clear();
                    }
                    else
                    {
                        contract.OverrideCreator = new ObjectCreator(constructor).CreateObject;
                        break;
                    }
                }
            }

            private string ToNamingCase(string name, bool toLower)
            {
                if(string.IsNullOrEmpty(name))
                    return name;

                if(toLower)
                {
                    if(!char.IsUpper(name[0]))
                        return name;
                }
                else
                {
                    if(!char.IsLower(name[0]))
                        return name;
                }

                char[] chars = name.ToCharArray();

                for(int i = 0; i < chars.Length; i++)
                {
                    bool hasNext = (i + 1 < chars.Length);

                    if(i > 0 && hasNext)
                    {
                        if(toLower)
                        {
                            if(!char.IsUpper(chars[i + 1]))
                                break;
                        }
                        else
                        {
                            if(!char.IsLower(chars[i + 1]))
                                break;
                        }
                    }

                    if(toLower)
                        chars[i] = char.ToLowerInvariant(chars[i]);
                    else
                        chars[i] = char.ToUpperInvariant(chars[i]);
                }

                return new string(chars);
            }

            private class ObjectCreator
            {
                private ConstructorInfo _constructor;

                public ObjectCreator(ConstructorInfo constructor)
                {
                    _constructor = constructor;
                }

                public object CreateObject(object[] parameters)
                {
                    return _constructor.Invoke(parameters);
                }
            }
        }

        #endregion
    }
}